/*
*
==============================================================================
*  Name        : 
*  Part of     : Omen Game Engine
*  Description : 
*  Version     : 1.0
*
*  Copyright (c) 2013 Lauri Kortevaara.
* 
==============================================================================
*/

#define OMEN_SOURCE 1

/// Omen Headers
#include "OmenMesh.h"
#include "OmenFactory.h"

using namespace Omen;

/// STL Headers
#include <iostream>
#include <algorithm>
#include <iterator>

Mesh::Mesh(Omen::IModel* pModel ) 
    :
    m_pModel(pModel),
    m_ptrVertexBuffer(NULL),
    m_ptrIndexBuffer(NULL),
    m_ptrVertexShader(NULL),
    m_pTexture(NULL),
    m_scaleVector(1.0,1.0,1.0),
    m_yaw(0.0), m_pitch(0.0), m_roll(0.0),
    m_pMaterial(NULL),
    m_aabb( Vector3d(0.0,0.0,0.0), Vector3d(0.0,0.0,0.0) ),
    m_boundingSphere( Vector3d(0.0,0.0,0.0), 0.0 ),
    m_bMeshHasChanged(true),
    m_bNormalsUpdated(false)
{
    m_pMaterial = GetOmenFactory().newMaterialObject();

    Omen::IKernel& kernel = GetOmenFactory().getKernel();
    // Register this as an event handler
    kernel.registerEventHandler(this);

    // Register event handlers
    registerEventFunc( this, &Mesh::OnFrameEvent );
}

Mesh::Mesh( const Mesh& other ) :
    m_aabb( Vector3d(0.0,0.0,0.0), Vector3d(0.0,0.0,0.0) ),
    m_boundingSphere( Vector3d(0.0,0.0,0.0), 0.0 ),
    m_bMeshHasChanged(true)
{
    m_ptrVertexBuffer = NULL;
    m_ptrIndexBuffer = NULL;

    m_ptrVertexShader = NULL;

    m_faces = other.m_faces;
    m_vertices = other.m_vertices;
    m_pTexture = NULL;

    m_scaleVector = other.m_scaleVector;
}

Mesh::~Mesh()
{
    m_faces.clear();
    m_vertices.clear();

    if( m_ptrVertexShader )
        delete m_ptrVertexShader;
    if( m_pTexture )
        delete m_pTexture;
}

/// <summary>Sets the pointer of parenting model</summary>
void Mesh::setModel( Omen::IModel* pModel )
{
    m_pModel = pModel;
}

/// <summary>Returns the pointer to parenting model</summary>
Omen::IModel* Mesh::model() const
{
    return m_pModel;
}

/// <summary>Returns the position of the mesh origo in global coordinate space</summary>
Omen::Vector3d  Mesh::globalPosition() const
{
    return transformation().row(3);
}

/// <summary>Returns the position of the mesh in model's/parent's coordinate space</summary>
Omen::Vector3d  Mesh::localPosition() const
{
    return m_transformation.row(3);
}

/// <summary>Returns the yaw,pitch and roll rotation angles of the mesh</summary>
void Mesh::getRotation( Omen::Float& yaw, Omen::Float& pitch, Omen::Float& roll ) const
{
    yaw = m_yaw; pitch = m_pitch; roll = m_roll;
}

/// <summary>Sets the yaw, pitch and roll rotation angles of the mesh</summary>
void Mesh::setRotation( Omen::Float yaw, Omen::Float pitch, Omen::Float roll )
{
    m_yaw = yaw; m_pitch = pitch; m_roll = roll;
    m_bMeshHasChanged = true;
}

/// <summary>Rotates the mesh around y-axis for given angle</summary>Float roll ){} 
void Mesh::rotateY( Omen::Float angle )
{
    m_yaw += angle;
    m_bMeshHasChanged = true;
}

/// <summary>Rotates the mesh around x-axis for given angle</summary>
void Mesh::rotateX( Omen::Float angle )
{
    m_pitch += angle;
    m_bMeshHasChanged = true;
}

/// <summary>Rotates the mesh around z-axis for given angle</summary>
void Mesh::rotateZ( Omen::Float angle )
{
    m_roll += angle;
    m_bMeshHasChanged = true;
}

/// <summary>Returns the transformation of this mesh</summary>
Omen::Matrix4x4  Mesh::transformation() const
{
    if( m_bMeshHasChanged )
    {
        Omen::Vector3d pos = m_transformation.row(3);
        m_transformation.setRow(3,Vector3d(0,0,0));
        m_transformation.setYawPitchRoll(m_yaw,m_pitch, m_roll);
        m_transformation.setRow(3,pos);
    }
    return m_transformation;
}

/// <summary>Translate the position of the mesh by given vector</summary>
void Mesh::translate( const Omen::Vector3d& v )
{
    Omen::Vector3d pos = m_transformation.row(3);
    pos += v;
    m_transformation.setRow(3,pos);
    m_bMeshHasChanged = true;
}

/// <summary>Translate the position of the mesh by given vector</summary>
void Mesh::setPosition( const Omen::Vector3d& v )
{
    m_transformation.setRow(3,v);
    m_bMeshHasChanged = true;
}

/// <summary>Render this mesh using given renderer</summary>
void Mesh::render( const IRenderer* pRenderer)
{
}

void Mesh::scale( Omen::Float& x, Omen::Float& y, Omen::Float& z )
{
    x = m_scaleVector.x;
    y = m_scaleVector.y;
    z = m_scaleVector.z;

    m_bMeshHasChanged = true;
}

void Mesh::setScale( Omen::Float x, Omen::Float y, Omen::Float z )
{
    m_scaleVector = Omen::Vector3d(x,y,z);
    
    m_bMeshHasChanged = true;
}

void Mesh::addVertex( const Omen::IMesh::Vertex& v )
{
    m_vertices.push_back(v);

    m_bMeshHasChanged = true;
}

unsigned long Mesh::vertexCount() const
{
    return m_vertices.size();
}

void Mesh::getVertices( Omen::IMesh::Vertex* pListVertices, unsigned long& maxSize )
{
    maxSize = min(maxSize,m_vertices.size() );
    for( unsigned long i = 0; i < maxSize; ++i )
        memcpy( &pListVertices[i], &m_vertices.at(i), sizeof( Omen::IMesh::Vertex ) );
}

bool Mesh::getVertex( unsigned long index, std::vector<IMesh::Vertex>::iterator& iter )
{
    bool bSuccess = false;
    if( index >= 0 && index < m_vertices.size() )
    {
        iter = m_vertices.begin()+index;
        bSuccess = true;
    }

    return bSuccess;
}

void Mesh::addFace( const Omen::IMesh::Face& face )
{
    Face f(face);
    f.pParentMesh = this;
    m_faces.push_back(f);

    m_bMeshHasChanged = true;
}

void Mesh::addFace( unsigned long p1, unsigned long p2, unsigned long p3 )
{
    IMesh::Face f;
    f.p1 = p1;
    f.p2 = p2;
    f.p3 = p3;
    f.pParentMesh = this;
    
    m_faces.push_back(f);

    m_bMeshHasChanged = true;
}

Vector3d Mesh::faceNormal( unsigned long faceIndex )
{
    return faceNormal(m_faces.at(faceIndex));
}

static inline Omen::Float sqr(Omen::Float& f ) { return f*f; }

Vector3d Mesh::faceNormal( const Omen::Mesh::Face& face )
{

    Omen::Float x1 = m_vertices[0].x;
    Omen::Float y1 = m_vertices[0].y;
    Omen::Float z1 = m_vertices[0].z;
    Omen::Float x2 = m_vertices[1].x;
    Omen::Float y2 = m_vertices[1].y;
    Omen::Float z2 = m_vertices[1].z;
    Omen::Float x3 = m_vertices[2].x;
    Omen::Float y3 = m_vertices[2].y;
    Omen::Float z3 = m_vertices[2].z;
    Omen::Float normx = (z1-z2)*(y3-y2)-(y1-y2)*(z3-z2);
    Omen::Float normy = (x1-x2)*(z3-z2)-(z1-z2)*(x3-x2);
    Omen::Float normz = (y1-y2)*(x3-x2)-(x1-x2)*(y3-y2);
    Omen::Float normlength = sqrt(sqr(normx)+sqr(normy)+sqr(normz));
    normx /= normlength;
    normy /= normlength;
    normz /= normlength;

    Omen::Vector3d n(normx,normy,normz);

    if( fabs(n.x) < 1.0e-5 )
        n.x = 0.0;
    if( fabs(n.y) < 1.0e-5 )
        n.y = 0.0;
    if( fabs(n.z) < 1.0e-5 )
        n.z = 0.0;
    return n;
}

void  Mesh::centralizeVertices()
{
    Omen::Vector3d vCenter = center();

    if( vertexCount() )
    {
        for( unsigned long vertexI = 0; vertexI < vertexCount(); ++vertexI )
        {
            Omen::Float x, y, z;
            x = m_vertices[vertexI].x * m_scaleVector.x - vCenter.x;
            y = m_vertices[vertexI].y * m_scaleVector.y - vCenter.y;
            z = m_vertices[vertexI].z * m_scaleVector.z - vCenter.z;
            
            m_vertices[vertexI].x = x / m_scaleVector.x;
            m_vertices[vertexI].y = y / m_scaleVector.y;
            m_vertices[vertexI].z = z / m_scaleVector.z;
        }
        
    }
    m_transformation.translate(vCenter);
    m_bMeshHasChanged = true;
}

/// Calculate the vertex normals and smoothen edeges
void Mesh::updateVertexNormals( Omen::Float limitSmoothAngle )
{
    if( m_bNormalsUpdated )
        return;

    unsigned long iv = 0;
    unsigned long size = m_vertices.size();
    if( size > 1000 )
        return;

    for( iv = 0; iv < size; ++iv )
    {
        Omen::Mesh::Vertex v = m_vertices.at(iv);
        Omen::Vector3d vertexNormal(0,0,0);

        // Find the faces that have the vertex iVertex
        std::vector<Mesh::Face> listFaces;
        for( auto iFace = m_faces.begin(); iFace != m_faces.end(); ++iFace )
            if( iFace->p1 == iv || iFace->p2 == iv || iFace->p3 == iv )
                listFaces.push_back(*iFace);

        // Go through all faces
        for( auto iFace = listFaces.begin(); iFace != listFaces.end(); ++iFace )
        {
            Vector3d n1 = faceNormal(*iFace);
            // Find adjacent faces
            for( auto iFaceAdj = listFaces.begin(); iFaceAdj < listFaces.end(); ++iFaceAdj )
            {
                if( iFace == iFaceAdj )
                    continue;

                std::vector<unsigned long> v1;
                unsigned long p1 = iFace->p1; 
                unsigned long p2 = iFace->p2; 
                unsigned long p3 = iFace->p3; 
                v1.push_back(p1);v1.push_back(p2);v1.push_back(p3);

                std::vector<unsigned long> v2;
                unsigned long ap1 = iFaceAdj->p1; 
                unsigned long ap2 = iFaceAdj->p2; 
                unsigned long ap3 = iFaceAdj->p3; 
                v2.push_back(ap1);v2.push_back(ap2);v2.push_back(ap3);

                std::sort(v1.begin(), v1.end());
                std::sort(v2.begin(), v2.end());
                std::vector<unsigned long> v3;
                std::set_intersection(v1.begin(), v1.end(), v2.begin(), v2.end(), std::back_inserter(v3));
                
                // If two faces share two vertices, they're adjacent
                if( v3.size() == 2 )
                {
                    Vector3d n2 = faceNormal(*iFaceAdj);
                    Omen::Float deltaNormal = acos(Vector3d::dotProduct(n1,n2))/Omen::PI_OVER_180;
                    // If the angle between two adjacent faces is larger than the given limit (by default 45-degrees)
                    // Create a new vertex for that corner for iFace, and set it's normal the face normal, instead of interpolated vertex normal
                    if( deltaNormal > limitSmoothAngle )
                    {
                        Omen::Mesh::Vertex cv1 = m_vertices[v3[0]];
                        Omen::Mesh::Vertex cv2 = m_vertices[v3[1]];
                        // Add new corner vertices and set them the face normal
                        cv1.nx = n1.x; cv1.ny = n1.y; cv1.nz = n1.z; iFace->replaceVertex(v3[0],m_vertices.size());addVertex(cv1);
                        cv2.nx = n1.x; cv2.ny = n1.y; cv2.nz = n1.z; iFace->replaceVertex(v3[1],m_vertices.size());addVertex(cv2);

                        Omen::Mesh::Vertex cv3 = m_vertices[v3[0]];
                        Omen::Mesh::Vertex cv4 = m_vertices[v3[1]];
                        // Add new corner vertices and set them the face normal
                        cv3.nx = n2.x; cv3.ny = n2.y; cv3.nz = n2.z; iFaceAdj->replaceVertex(v3[0],m_vertices.size());addVertex(cv3);
                        cv4.nx = n2.x; cv4.ny = n2.y; cv4.nz = n2.z; iFaceAdj->replaceVertex(v3[1],m_vertices.size());addVertex(cv4);
                    }

                    // No need to find any more adjacent faces
                    break;
                }
            }
            vertexNormal += n1;
        }
        // Now we have gone through all the faces around this vertex and summed up the vertex normal
        // Now normalize is
        vertexNormal.normalize();
        m_vertices[iv].nx = vertexNormal.x;
        m_vertices[iv].ny = vertexNormal.y;
        m_vertices[iv].nz = vertexNormal.z;
    }

    m_bNormalsUpdated = true;
    m_bMeshHasChanged = true;
}

unsigned long Mesh::faceCount() const
{
    return m_faces.size();
}

void Mesh::getFaces( Omen::IMesh::Face** pListFaces, unsigned long& maxSize )
{
    maxSize = min(maxSize,m_faces.size() );
    for( unsigned long i = 0; i < maxSize; ++i )
        pListFaces[i] = &m_faces.at(i);
}

bool Mesh::getFace( unsigned long index, std::vector<IMesh::Face>::iterator& iter )
{
    bool bSuccess = false;
    if( index >= 0 && index < m_vertices.size() )
    {
        iter = m_faces.begin()+index;
        bSuccess = true;
    }

    return bSuccess;
}

///
/// Vertex and index buffers
///

/// <summary>Set Renderer specific vertex buffer pointer</summary>
void Mesh::setVertexBufferPtr( void* ptrVertexBuffer )
{
    m_ptrVertexBuffer = ptrVertexBuffer;
}

/// <summary>Set Renderer specific index buffer pointer</summary>
void Mesh::setIndexBufferPtr( void* ptrIndexBuffer )
{
    m_ptrIndexBuffer = ptrIndexBuffer;
}

/// <summary>Returns the Renderer specific vertex buffer pointer </summary>
void* Mesh::vertexBufferPtr() const
{
    return m_ptrVertexBuffer;
}

/// <summary>Returns the Renderer specific index buffer pointer </summary>
void* Mesh::indexBufferPtr() const
{
    return m_ptrIndexBuffer;
}

/// <summary>Set Renderer specific vertex shader pointer</summary>
void Mesh::setVertexShaderPtr( Omen::IShader* ptrVertexShader )
{
    m_ptrVertexShader = ptrVertexShader;
}

/// <summary>Returns the Renderer specific vertex shader pointer </summary>
Omen::IShader* Mesh::vertexShaderPtr() const
{
    return m_ptrVertexShader;
}

// Texture
void Mesh::setTexture( Omen::ITexture* pTexture )
{
    m_pTexture = pTexture;
    m_bMeshHasChanged = true;
}

ITexture* Mesh::texture() const
{
    return m_pTexture;
}

IMaterial* Mesh::material( unsigned long faceIndex ) const
{
    if( faceIndex == -1 || faceIndex >= m_faces.size())
        return m_pMaterial;
    else
    {
        return m_faces[faceIndex].pMaterial;
    }

}

void Mesh::setMaterial( Omen::IMaterial* pMaterial )
{
    if( m_pMaterial )
        delete m_pMaterial;
    m_pMaterial = pMaterial;
}

const Omen::Sphere& Mesh::boundingSphere() const
{
    return m_boundingSphere;
}
const Omen::AABB& Mesh::boundingBox() const
{
    return m_aabb;
}

/// <summary>Returns true whether the mesh has changed since last frame</summary>
bool Mesh::hasChanged() const
{
    return m_bMeshHasChanged;
}

/// <summary>Updates the bounding volumes of the mesh</summary>
void Mesh::updateBoundingVolumes()
{
    if( m_bMeshHasChanged )
    {
        std::vector<Omen::Float> scales;
        std::wstring strScale;

        Omen::Float minX, minY, minZ;
        Omen::Float maxX, maxY, maxZ;

        minX =  minY = minZ =  1.0e10;
        maxX =  maxY = maxZ = -1.0e10;

        Omen::Float radMax = 0.0;
        Vector3d center = globalPosition();

        for( unsigned long vertexI = 0; vertexI < vertexCount(); ++vertexI )
        {
            Omen::Float x, y, z;
            x = m_vertices[vertexI].x * m_scaleVector.x;
            y = m_vertices[vertexI].y * m_scaleVector.y;
            z = m_vertices[vertexI].z * m_scaleVector.z;

            Vector3d pos(x,y,z);
            pos = transformation()*pos;
            x = pos.x; y = pos.y; z = pos.z;
            minX = min(minX,x); minY = min(minY,y); minZ = min(minZ,z);
            maxX = max(maxX,x); maxY = max(maxY,y); maxZ = max(maxZ,z);

            Omen::Float rad = sqrt(x*x + y*y + z*z);
            radMax = max( rad, radMax );
        }

        minX += center.x; maxX+=center.x;
        minY += center.y; maxY+=center.y;
        minZ += center.z; maxZ+=center.z;

        m_aabb.setMinimum( Omen::Vector3d(minX,minY,minZ) );
        m_aabb.setMaximum( Omen::Vector3d(maxX,maxY,maxZ) );
        //std::cout << "Minimum aabb : (" << minX << ", " << minY << ", " << minZ << ")" << std::endl;
        //std::cout << "Maximum aabb : (" << maxX << ", " << maxY << ", " << maxZ << ")" << std::endl;

        m_boundingSphere.setRadius(radMax);
        m_boundingSphere.setPosition(transformation().row(3));
        
        // Invalidate the mesh has changed flag
        m_bMeshHasChanged = false;
    }
}

Omen::Vector3d Mesh::center()
{
    Omen::Vector3d vCenter(0.0,0.0,0.0);

    if( vertexCount() )
    {
        for( unsigned long vertexI = 0; vertexI < vertexCount(); ++vertexI )
        {
            Omen::Float x, y, z;
            x = m_vertices[vertexI].x * m_scaleVector.x;
            y = m_vertices[vertexI].y * m_scaleVector.y;
            z = m_vertices[vertexI].z * m_scaleVector.z;
            vCenter += Omen::Vector3d(x,y,z);
        }
        vCenter /= static_cast<Omen::Float>(vertexCount());
    }

    return vCenter;
}

void Mesh::OnFrameEvent( const Omen::FrameEvent* pEvent )
{
    if( pEvent )
    {
        if( m_bMeshHasChanged )
            m_pModel->setUpdateNeeded(true);
    }
}


/// EOF